#ifndef __M_DB_H__
#define __M_DB_H__
#include "util.hpp"
#include <cassert>
#include <mutex>

class user_table
{
public:
    user_table(const std::string &host,
               const std::string &username,
               const std::string &password,
               const std::string &dbname,
               uint16_t port = 3306)
               {
                _mysql = mysql_util::mysql_create(host, username, password, dbname, port);
                assert(_mysql);
               }
            
    ~user_table()
    {
        mysql_util::mysql_destroy(_mysql);
        _mysql = nullptr;
    }

    //新增用户
    bool insert(Json::Value &user)
    {
        #define INSERT_USER "insert into user values(null, '%s', password('%s'), 1000, 0, 0);"
        //检查用户数据的完整性
        if (user["username"].isNull() || user["password"].isNull())
        {
            DLOG("INPUT PASSWORD OR USERNAME");
            return false;
        }

        char sql[4096] = {0};//用来存储格式化后的sql语句
        sprintf(sql, INSERT_USER, user["username"].asCString(), user["password"].asCString());
        bool ret = mysql_util::mysql_exec(_mysql, sql);
        if (ret == false)
        {
            DLOG("insert user info failed!!\n");
            return false;
        }

        return true;
    }

    //登录验证并返回用户的详细信息
    bool login(Json::Value &user)
    {
        if (user["password"].isNull() || user["username"].isNull())
        {
            DLOG("INPUT PASSWORD OR USERNAME");
            return false;
        }

        //以用户名和密码共同作为查询的条件
        #define LOGIN_USER "select id, score, total_count, win_count from user where username='%s' and password=password('%s');"
        char sql[4096] = {0};
        sprintf(sql, LOGIN_USER, user["username"].asCString(), user["password"].asCString());

        MYSQL_RES *res = nullptr;
        {//使用锁是为了防止在多线程的情况下，数据不一致的问题，保证了操作的原子性
        //例如：一个线程正在从数据库获取结果集，另一个线程可能同时执行了一个新的查询，这可能会干扰前一个线程对结果集的正确获取和处理
            std::unique_lock<std::mutex> lock(_mutex);
            bool ret = mysql_util::mysql_exec(_mysql, sql);
            if (ret == false)
            {
                DLOG("user login failed!!\n");
                return false;
            }

            //获取结果集，并且结果集中只能有一条数据
            res = mysql_store_result(_mysql);
            if (res == nullptr)
            {
                DLOG("have no user information!!\n");
                return false;
            }
        }

        int row_num = mysql_num_rows(res);
        if (row_num != 1)
        {
            DLOG("the user iniformation queried is not unique!!");
            return false;
        }

        //获取结果集中的数据
        MYSQL_ROW row = mysql_fetch_row(res);//遍历结果集, 并且这个接⼝会保存当前读取结果位置，每次获取的都是下⼀条数据
        user["id"] = (Json::UInt64)std::stol(row[0]);
        user["score"] = (Json::UInt64)std::stol(row[1]);
        user["total_count"] = std::stoi(row[2]);
        user["win_count"] = std::stoi(row[3]);
        mysql_free_result(res);
        return true;
    }

    //通过用户名获取用户信息
    bool select_by_name(const std::string& name, Json::Value& user)
    {
        #define USER_BY_NAME "select id, score, total_count, win_count from user where username='%s';"
        char sql[4096] = {0};
        sprintf(sql, USER_BY_NAME, name.c_str());
        MYSQL_RES *res = nullptr;
        {
            std::unique_lock<std::mutex> lock(_mutex);
            bool ret = mysql_util::mysql_exec(_mysql, sql);
            if (ret == false)
            {
                DLOG("get user by name failed!!\n");
                return false;
            }

            res = mysql_store_result(_mysql);
            if (res == nullptr)
            {
                DLOG("have no user information!!\n");
                return false;
            }
        }

        int ret = mysql_num_rows(res);
        if (ret != 1)
        {
            DLOG("the user information queried is not unique!!\n");
            return false;
        }

        MYSQL_ROW row = mysql_fetch_row(res);
        user["id"] = (Json::UInt64)std::stol(row[0]);
        user["username"] = name;
        user["score"] = (Json::UInt64)std::stol(row[1]);
        user["total_count"] = std::stoi(row[2]);
        user["win_count"] = std::stoi(row[3]);
        mysql_free_result(res);
        return true;
    }

    //通过id获取用户信息
    bool select_by_id(uint64_t id, Json::Value &user)
    {
        #define USER_BY_ID "select username, score, total_count, win_count from user where id=%d;"

        char sql[4096] = {0};
        sprintf(sql, USER_BY_ID, id);
        MYSQL_RES *res = nullptr;
        {
            std::unique_lock<std::mutex> lock(_mutex);
            bool ret = mysql_util::mysql_exec(_mysql, sql);
            if (ret == false)
            {
                DLOG("get user by id failed!!\n");
                return false;
            }

            res = mysql_store_result(_mysql);
            if (res == nullptr)
            {
                DLOG("have no user information!!\n");
                return false;
            }
        }

        int ret = mysql_num_rows(res);
        if (ret != 1)
        {
            DLOG("the user information is not unique!!\n");
            return false;
        }

        MYSQL_ROW row = mysql_fetch_row(res);
        user["id"] = (Json::UInt64)id;
        user["username"] = row[0];
        user["score"] = (Json::UInt64)std::stol(row[1]);
        user["total_count"] = std::stoi(row[2]);
        user["win_count"] = std::stoi(row[3]);
        mysql_free_result(res);
        return true;
    }

    //胜利时，天梯分数+30，战斗场次+1，胜利场次+1
    bool win(uint64_t id)
    {
        #define USER_WIN "update user set score=score+30, total_count=total_count+1, win_count=win_count+1 where id=%d;"
        char sql[4096] = {0};
        sprintf(sql, USER_WIN, id);
        //更新操作都是基于特定的用户id进行的单次更新，且每次更新的操作逻辑相对简单和明确，不会因为并发执行而导致数据的混乱或不一致，因此不需要使用锁
        bool ret = mysql_util::mysql_exec(_mysql, sql);
        if (ret == false)
        {
            DLOG("update win user information failed!!\n");
            return false;
        }
        return true;
    }

    //失败时，天体分数-30，战斗场次+1，其他不变
    bool lose(uint64_t id)
    {
        #define USER_LOSE "update user set score=score-30, total_count=total_count+1 where id=%d;"
        char sql[4096] = {0};
        sprintf(sql, USER_LOSE, id);
        bool ret = mysql_util::mysql_exec(_mysql, sql);
        if (ret == false)
        {
            DLOG("update lose user information failed!!\n");
            return false;
        }
        return true;
    }

private:
    MYSQL *_mysql;
    std::mutex _mutex;
};
#endif